-
Notifications
You must be signed in to change notification settings - Fork 82
MrBean: Fix detection of inherited default method in Java 8+ interface #102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…efault methods in Java 8+ interfaces
for (JavaType curr = implementedType; (curr != null) && !curr.isJavaLangObject(); | ||
curr = curr.getSuperClass()) { | ||
// 29-Nov-2015, tatu: Avoiding exceptions would be good, so would linear scan | ||
// be better here? | ||
try { | ||
Method effectiveMethod = curr.getRawClass().getDeclaredMethod(name, argTypes); | ||
if (effectiveMethod != null && BeanUtil.isConcrete(effectiveMethod)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getDeclaredMethod
(like getMethod
) never returns null
public abstract String getX(); | ||
|
||
public String getFoo() { return "Foo!"; } | ||
public void setY(int value) { y = value; } | ||
|
||
// also verify non-public methods | ||
protected abstract String getZ(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we are expected to implement non-public getters... although probably makes sense (setters def. need to be implemented).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure whether non-public methods were supported until I wrote the test, which I decided to do after exploring the differences between getMethod
and getDeclaredMethod
. Once I realized that non-public methods were working the same as public ones, I figured it was worthwhile to create a test to verify that behavior wasn't regressed, per Hyrum's law.
@@ -176,16 +176,28 @@ protected boolean hasConcreteOverride(Method m0, JavaType implementedType) | |||
{ | |||
final String name = m0.getName(); | |||
final Class<?>[] argTypes = m0.getParameterTypes(); | |||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I am specifically trying to understand this part here; why is the extra check needed?
Is that because existing code seems to only look for supertypes (and not type itself) or something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit convoluted (and took me some time to spot the issue), so let me illustrate with the BeanWithInheritedDefault
example from the unit test:
In BeanBuilder.implement
,ArrayList<JavaType> implTypes
contains [BeanWithInheritedDefault, BeanWithDefaultForOtherInterface, Bean, OtherInterface]
, and these reduce two relevant executions of the loop with Method m
:
BeanWithDefaultForOtherInterface.anyValuePresent
, which is concrete itself, and is properly skipped at line 108OtherInterface.anyValuePresent
, which is not, and therefore which calls thehasConcreteOverride
method I've modified.
In the original implementation of hasConcreteOverride
, it checks for a concrete declared method on the implementedType
(in this case BeanWithInheritedDefault
, which doesn't have it), and then it checks on superclasses of which there is none. Critically, this code fails to locate the concrete implementation of this method in BeanWithDefaultForOtherInterface
, and therefore it incorrectly return false from the method.
I started to explore solutions that would either (a) traverse the interface hierarchy, or (b) optimize the loop in implement
to track known-concrete methods, but I ultimately realized this simple addition to hasConcreteOverride
solves the search problem for all public methods, which coincidentally applies to all Java interfaces and their default methods. I think it also makes the hasConcreteOverride
more efficient, since it is only necessary to search the superclass hierarchy when the method is in question is non-public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok. So the logic was ok pre-Java8, but the introduction of interface default implementations threw the monkey wrench... makes sense.
First of all, thank you very much for contributing this patch! Couple of practical things:
The challenge with (2) would be Java 8; I want the fix in 2.12 but that can only use Java 8. If you can create separate PR(s), making those against 2.12 would be great (at least for fixes part; tests I can probably merge manually if need be). |
|
Ok... I've created separate PRs for (103) the fix, (104) passing tests in 2.12, (105) both of those plus the test for the fix, all against 2.12. I'll hopefully deliver my CLA next week, or let you know if it is delayed for some reason. |
CLA is still pending... it took until today to get it in front of the right people for review. I'll keep you posted next week. |
@cowtowncoder I've sent our Corporate CLA to the address you specified. I've also created a fork from our organization, and pushed my branch there. Are you able to change this PR to merge from https://github.com/bazaarvoice/jackson-modules-base/tree/test-default-in-interfaces instead? I don't seem to have that permission. If necessary, I can create a new PR, but I was hoping to make the change here to retain the existing context. |
@robbytx Ok I am finally back looking at this: apologies for slow follow-up. CCLA is in place and all. It looks like this PR and 103, 104 and 105 are overlapping, plus you mentioned that it'd make sense to rather merge from BV repo. |
@cowtowncoder No problem at all on follow-up time -- you've been waiting much longer for me for CLA. Also, I didn't intend for you to have to create a new PR from the BV repo -- I was hoping that you were able to simply edit this PR to switch the source to that repo. I know this is possible to do within a single organization, but GitHub is not allowing me to update my PRs in this repo, and I thought you might be able to do that. Finally, I'm sorry for my confusion on which branch(es) to target... I should have read CONTRIBUTING earlier. If possible, I would love for my patches (which I believe are purely bug fixes) to go into a new patch version on To recap, here's a quick summary of the current commits/branches for this patch:
It seems like (4) is what you should ultimately merge into At this point:
|
@robbytx Ok, that makes sense. So, on 2.11 vs 2.12... it's a judgment call: since there is no API change, this could go in 2.11 (or even in 2.10 if there was specific need). My only concern is wrt risk, how likely it is that something else might break. But I think that with all of that, 2.11 would be fine. |
Closing this in favor of #109, which targets 2.11. |
Problem
When the type to materialize extends/implements some interface that has a
default
method implementation (with a non-bean method name), MrBean will treat the method as if it lacks an implementation.Solution
Use
Class.getMethod
to locate the exact method that the implemented type will utilize, which properly handles this case.Verification
I've added several unit tests to cover the fixed behavior, and to verify existing, related behavior:
TestSimpleMaterializedInterfaces.testInheritedDefaultMethodInInterface
is the test case that exercise the current issue, which is now resolved.TestSimpleMaterializedInterfaces.testDefaultMethodInInterface
is a simpler test case that is already passing with the existing implementation.TestAbstractClasses
andTestAbstractClassesWithOverrides
have been extended to exercise some cases that justify the original code inhasConcreteOverride
. These test cases were already passing with the existing implementation.